﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace GFluentCode.Tools.FluentObject
{
    /// <summary>
    /// Socket
    /// </summary>
    public class FluentSocketServer : INotifyPropertyChanged
    {
        #region 属性更新
        /// <summary>
        /// 属性变化事件
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// 属性更新
        /// </summary>
        /// <param name="propertyName"></param>
        protected virtual void OnPropertyChanged(string propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        #region

        /// <summary>
        /// 数据接收委托
        /// </summary>
        public delegate void ReceivedHandler(FluentSocket socket, byte[] data);
        /// <summary>
        /// 数据接收事件
        /// </summary>
        public event ReceivedHandler ReceivedData;

        /// <summary>
        /// 接收连接委托
        /// </summary>
        /// <param name="socket"></param>
        public delegate void AcceptHandler(FluentSocket socket);
        /// <summary>
        /// 接收连接请求
        /// </summary>
        public event AcceptHandler AcceptSocket;

        #endregion

        /// <summary>
        /// 监听
        /// </summary>
        Socket _listenSocket;

        /// <summary>
        /// 用于每个I/O Socket操作的缓冲区大小
        /// </summary>
        int _bufferSize;

        /// <summary>
        /// 服务器上连接的客户端总数
        /// </summary>
        int _numConnectedSockets;

        /// <summary>
        /// 服务器能接受的最大连接数量
        /// </summary>
        int _numConnections;

        /// <summary>
        /// 连接池
        /// </summary>
        FluentSocketPool _contextPool;

        private string _msg;
        /// <summary>
        /// 消息
        /// </summary>
        public string Msg { get => _msg; set { _msg = value; OnPropertyChanged(nameof(Msg)); } }

        /// <summary>
        /// 是否启动监听
        /// </summary>
        public bool IsListening { get; internal set; }

        /// <summary>
        /// 连接超时判断时长 单位秒 最小60
        /// </summary>
        public int TimeoutLen { get; set; } = 60;

        /// <summary>
        /// 超时连接
        /// </summary>
        Thread _deamonThread;

        /// <summary>
        /// 构造函数，建立一个未初始化的服务器实例
        /// </summary>
        /// <param name="numConnections">服务器的最大连接数据</param>
        /// <param name="bufferSize">用于每个I/O Socket操作的缓冲区大小</param>
        public FluentSocketServer(int numConnections = 1000, int bufferSize = 1024)
        {
            if (numConnections <= 0) numConnections = 1000;
            if (bufferSize <= 0) bufferSize = 1024;

            _numConnectedSockets = 0;
            _numConnections = numConnections;
            _bufferSize = bufferSize;

            _contextPool = new FluentSocketPool(numConnections);

            for (var i = 0; i < _numConnections; i++)
            {
                var context = new SocketAsyncEventArgs();
                context.Completed += ContextIO_Complted;
                context.SetBuffer(new byte[_bufferSize], 0, _bufferSize);

                _contextPool.Add(context);
            }

            _deamonThread = new Thread(DeamonThreadRun) { IsBackground = true };
        }

        void DeamonThreadRun()
        {
            while (_deamonThread.IsAlive && IsListening)
            {
                TimeoutLen = TimeoutLen < 60 ? 60 : TimeoutLen;
                Thread.Sleep(TimeoutLen * 1000);

                var len = _contextPool.Count;
                for (var i = len - 1; i >= 0; i--)
                {
                    if (_contextPool[i] == null) continue;

                    var socket = _contextPool[i].UserToken as FluentSocket;

                    if (socket == null) continue;
                    else if ((DateTime.Now - socket.LastDate).TotalSeconds > TimeoutLen) Close(_contextPool[i]);
                }

            }

            _deamonThread = new Thread(DeamonThreadRun) { IsBackground = true };
        }

        /// <summary>
        /// 异步操作完成
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ContextIO_Complted(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    ProcessSend(e);
                    break;
                default:
                    throw new ArgumentException("The last operation completed on the socket was not a receive or send");
            }
        }

        #region 监听

        #region 启动监听
        /// <summary>
        /// 启动监听
        /// </summary>
        /// <param name="port"></param>
        /// <param name="pt">指定 System.Net.Sockets.Socket 类支持的协议。  TCP/UDP</param>
        public void Start(int port, ProtocolType pt = ProtocolType.Tcp)
        {
            // 获得主机相关信息
            var addressList = Dns.GetHostEntry(Environment.MachineName).AddressList;
            var localEndPoint = new IPEndPoint(addressList[addressList.Length - 1], port);

            // 创建监听socket
            if (pt == ProtocolType.Tcp)
                _listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            else if (pt == ProtocolType.Udp)
                _listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Tcp);

            _listenSocket.ReceiveBufferSize = _bufferSize;
            _listenSocket.SendBufferSize = _bufferSize;

            if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
            {
                // 配置监听socket为 dual-mode (IPv4 & IPv6) 
                // 27 is equivalent to IPV6_V6ONLY socket option in the winsock snippet below,
                _listenSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);
                _listenSocket.Bind(new IPEndPoint(IPAddress.IPv6Any, localEndPoint.Port));
            }
            else
            {
                _listenSocket.Bind(localEndPoint);
            }

            // 开始监听
            _listenSocket.Listen(_numConnections);

            // 在监听Socket上投递一个接受请求。
            StartAccept(null);

            _deamonThread.Start();
            IsListening = true;
        }

        /// <summary>
        /// 启动
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        /// <param name="pt"></param>
        public void Start(string ip, int port, ProtocolType pt = ProtocolType.Tcp)
        {
            // 获得主机相关信息            
            var localEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);

            // 创建监听socket
            if (pt == ProtocolType.Tcp)
                _listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            else if (pt == ProtocolType.Udp)
                _listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Tcp);

            _listenSocket.ReceiveBufferSize = _bufferSize;
            _listenSocket.SendBufferSize = _bufferSize;

            if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
            {
                // 配置监听socket为 dual-mode (IPv4 & IPv6) 
                // 27 is equivalent to IPV6_V6ONLY socket option in the winsock snippet below,
                _listenSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);
                _listenSocket.Bind(new IPEndPoint(IPAddress.IPv6Any, localEndPoint.Port));
            }
            else
            {
                _listenSocket.Bind(localEndPoint);
            }

            // 开始监听
            _listenSocket.Listen(_numConnections);

            // 在监听Socket上投递一个接受请求。
            StartAccept(null);

            _deamonThread.Start();
            IsListening = true;
        }
        #endregion

        #region 监听处理

        void StartAccept(SocketAsyncEventArgs acceptAsyncEventArgs)
        {
            if (acceptAsyncEventArgs == null)
            {
                acceptAsyncEventArgs = new SocketAsyncEventArgs();
                acceptAsyncEventArgs.Completed += AcceptEventArgsCompleted;
            }
            else
            {
                acceptAsyncEventArgs.AcceptSocket = null;
            }

            if (_listenSocket.AcceptAsync(acceptAsyncEventArgs))
                ProcessAccept(acceptAsyncEventArgs);
        }

        void AcceptEventArgsCompleted(object send, SocketAsyncEventArgs acceptEventArgs)
        {
            ProcessAccept(acceptEventArgs);
        }

        void ProcessAccept(SocketAsyncEventArgs e)
        {
            var s = e.AcceptSocket;
            if (!s.Connected) return;

            var socket = new FluentSocket { SocketObj = s };
            try
            {
                SocketAsyncEventArgs ioContext = _contextPool.Pop();
                if (ioContext != null)
                {
                    // 从接受的客户端连接中取数据配置ioContext
                    socket.RecevieArgs = ioContext;

                    ioContext.UserToken = socket;

                    Interlocked.Increment(ref _numConnectedSockets);

                    Msg = $"客户{s.RemoteEndPoint.ToString()}连入,当前有{_numConnectedSockets}连接,共{_numConnections}连接。";

                    AcceptSocket?.Invoke(socket);

                    if (!s.ReceiveAsync(ioContext))
                    {
                        this.ProcessReceive(ioContext);
                    }
                }
                else
                {
                    //已经达到最大客户连接数量，在这接受连接，发送“连接已经达到最大数”，然后断开连接

                    s.Send(Encoding.UTF8.GetBytes("连接已经达到最大数!"));

                    Msg = $"连接已满，拒绝 {s.RemoteEndPoint} 的连接。";

                    s.Close();
                }
            }
            catch (SocketException ex)
            {
                var fs = e.UserToken as FluentSocket;
                var token = fs.SocketObj;

                Msg = $"接收客户 {token.RemoteEndPoint} 数据出错, 异常信息： {ex.ToString()} 。";

            }
            catch (Exception ex)
            {
                Msg = $"异常:{ex.ToString()}";
            }
            // 投递下一个接受请求
            StartAccept(e);
        }
        #endregion

        #region 关闭

        /// <summary>
        /// 关闭
        /// </summary>
        public void Stop()
        {
            _listenSocket.Close();

            IsListening = false;
        }
        #endregion

        #endregion

        #region 接收数据
        void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred <= 0)
            {
                Close(e);
                return;
            }

            if (e.SocketError != SocketError.Success)
            {
                ProcessError(e);
                return;
            }

            var socket = e.UserToken as FluentSocket;
            socket.LastDate = DateTime.Now;

            //判断所有需接收的数据是否已经完成
            if (socket.SocketObj.Available == 0)
            {
                //取出接收的数据
                var arr = new byte[e.BytesTransferred];
                Array.Copy(e.Buffer, 0, arr, 0, arr.Length);

                ReceivedData?.Invoke(socket, arr);
            }
            //为接收下一段数据，投递接收请求
            if (!socket.SocketObj.ReceiveAsync(e)) ProcessReceive(e);
        }
        #endregion

        #region 发送

        void ProcessSend(SocketAsyncEventArgs e)
        {
            if (e.SocketError != SocketError.Success)
            {
                ProcessError(e);
                return;
            }

            var socket = e.UserToken as FluentSocket;
            socket.LastDate = DateTime.Now;

            e.SetBuffer(0, _bufferSize);
            if (!socket.SocketObj.ReceiveAsync(e)) ProcessReceive(e);
        }
        #endregion

        #region 错误

        /// <summary>
        /// 错误处理
        /// </summary>
        /// <param name="e"></param>
        void ProcessError(SocketAsyncEventArgs e)
        {
            var s = e.UserToken as FluentSocket;
            var localEp = s.SocketObj.LocalEndPoint as IPEndPoint;

            Close(s, e);

            string outStr = String.Format("套接字错误 {0}, IP {1}, 操作 {2}。", (Int32)e.SocketError, localEp, e.LastOperation);
            Msg = $"套接字错误{(int)e.SocketError},IP【{localEp}】,操作：{e.LastOperation}";
        }
        #endregion

        #region 关闭
        /// <summary>
        /// 关闭
        /// </summary>
        /// <param name="e"></param>
        void Close(SocketAsyncEventArgs e)
        {
            var socket = e.UserToken as FluentSocket;
            Close(socket, e);
        }
        /// <summary>
        /// 关闭
        /// </summary>
        /// <param name="s"></param>
        /// <param name="e"></param>
        void Close(FluentSocket s, SocketAsyncEventArgs e)
        {
            Interlocked.Decrement(ref _numConnectedSockets);

            _contextPool.Push(e);

            try
            {
                s.SocketObj?.Shutdown(SocketShutdown.Send);
            }
            catch (Exception) { }
            finally
            {
                s.SocketObj?.Close();
            }
        }
        #endregion

        #region 发送

        /// <summary>
        /// 发送
        /// </summary>
        /// <param name="id"></param>
        /// <param name="data"></param>
        public void Send(string id, byte[] data)
        {
            var tmp = _contextPool[id];
            if (tmp == null) return;

            var socket = tmp.UserToken as FluentSocket;
            if (socket == null) return;

            socket.SendArgs.SetBuffer(data, 0, data.Length);

            if (!socket.SocketObj.SendAsync(socket.SendArgs)) ProcessSend(socket.SendArgs);
        }
        #endregion
    }

    #region Socket对象

    /// <summary>
    /// FluentSocket对象继承Socket,并做一定的扩展
    /// </summary>
    public sealed class FluentSocket //: Socket
    {
        #region       
        /// <summary>
        /// ID标识,也可按需求赋自定标识
        /// </summary>
        public string ID { get; internal set; } = Guid.NewGuid().ToString("N");
        /// <summary>
        /// 套接实体
        /// </summary>
        public Socket SocketObj { get; set; }

        /// <summary>
        /// 连接时间
        /// </summary>
        public DateTime FirstDate { get; } = DateTime.Now;
        /// <summary>
        /// 最后通讯时间
        /// </summary>
        public DateTime LastDate { get; set; } = DateTime.Now;

        /// <summary>
        /// 接收异步套接字
        /// </summary>
        public SocketAsyncEventArgs RecevieArgs { get; internal set; }
        /// <summary>
        /// 发送异步套接字
        /// </summary>
        public SocketAsyncEventArgs SendArgs { get; } = new SocketAsyncEventArgs();
        #endregion
    }
    #endregion

    #region 连接池
    /// <summary>
    /// 连接池
    /// </summary>
    internal class FluentSocketPool
    {
        /// <summary>
        /// 为每一个Socket客户端分配一个SocketAsyncEventArgs
        /// </summary>
        List<SocketAsyncEventArgs> _pool;
        /// <summary>
        /// pool对象池的容量
        /// </summary>
        int _capacity;
        /// <summary>
        /// 已分配和未分配对象的边界，大的是已经分配的，小的是未分配的
        /// </summary>
        int _boundary;
        /// <summary>
        /// 当前连接数
        /// </summary>
        public int Count { get => _pool == null ? 0 : _pool.Count; }

        internal FluentSocketPool(int capacity)
        {
            _pool = new List<SocketAsyncEventArgs>();
            _boundary = 0;
            _capacity = capacity;
        }

        internal SocketAsyncEventArgs this[int index]
        {
            get
            {
                lock (_pool)
                {
                    if (index < 0) return null;
                    else if (_pool.Count <= index) return null;
                    else return _pool[index];
                }
            }
        }

        internal SocketAsyncEventArgs this[string id]
        {
            get
            {
                lock (_pool)
                {
                    for (var i = 0; i < _pool.Count; i++)
                    {
                        if (_pool[i].UserToken == null) continue;
                        var s = _pool[i].UserToken as FluentSocket;
                        if (s == null) continue;
                        if (!s.ID.Equals(id)) continue;
                        return _pool[i];
                    }
                    return null;
                }
            }
        }
        /// <summary>
        /// 往pool对象池中增加新建立的对象，因为这个程序在启动时会建立好所有对象，
        /// 故这个方法只在初始化时会被调用,因此，没有加锁。
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        internal bool Add(SocketAsyncEventArgs arg)
        {
            if (arg != null && _pool.Count < _capacity)
            {
                _pool.Add(arg);
                _boundary++;
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// 从对象池中取出一个对象，交给一个socket来进行投递请求操作
        /// </summary>
        /// <returns></returns>
        internal SocketAsyncEventArgs Pop()
        {
            lock (_pool)
            {
                if (_boundary > 0)
                {
                    --_boundary;
                    return _pool[_boundary];
                }
                else
                    return null;
            }
        }

        /// <summary>
        /// 一个socket客户断开，与其相关的被释放，重新投入Pool中，备用。
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        internal bool Push(SocketAsyncEventArgs arg)
        {
            if (arg != null)
            {
                lock (_pool)
                {
                    //找出被断开的客户,此处一定能查到，因此index不可能为-1，必定要大于0。
                    int index = _pool.IndexOf(arg, _boundary);
                    //不知道为什么第一次成功之后还会再进来一次，所以先这样处理
                    if (index == -1) return true;
                    //正好是边界元素
                    if (index == _boundary)
                        _boundary++;
                    else
                    {
                        //将断开客户移到边界上，边界右移
                        _pool[index] = _pool[_boundary];
                        _pool[_boundary++] = arg;
                    }
                }
                return true;
            }
            else
                return false;
        }
    }
    #endregion
}
